package com.lin.filegraph.utils.methoddg;

import com.lin.filegraph.utils.parser.node.JMethodInvocationNode;
import com.lin.filegraph.utils.parser.node.JMethodNode;
import com.lin.filegraph.utils.parser.node.JProjectNode;
import com.lin.filegraph.utils.parser.simpleast.SimpleASTHelper;

import java.util.ArrayList;
import java.util.List;

/**
 * @description:
 * @author: linhuaixu
 * @time: 2023/5/18 10:39
 */
public class MethodGraph {

    private JProjectNode root = null;
    private List<JMethodNode> listOfMethods;
    private List<MethodEdge> listOfMethodEdges;
    public MethodGraph(JProjectNode root) {

        this.root = root;
        listOfMethods = SimpleASTHelper.findAllMethodsInSubTree(root);
        listOfMethodEdges = new ArrayList<>();
        createEdges();
        calculateCallDepth();
    }

    public void addEdge(MethodEdge methodEdge) {
        listOfMethodEdges.add(methodEdge);
    }

    public int getMaxCallDepth() {
        calculateCallDepthInComp();
        int max = 0;
        for (JMethodNode methodNode : listOfMethods) {
            if (max <= methodNode.getCallDepthInComp()) {
                max = methodNode.getCallDepthInComp();
            }
        }
        return max;
    }

    private void createEdges() {
        for (JMethodNode method : listOfMethods) {
            for (JMethodInvocationNode methodIvk : method.getListMethodInvokeNode()) {
                JMethodNode declaringMethod = methodIvk.getDeclaringMethod();
                if (declaringMethod != null && !declaringMethod.equals(method)) {
                    if (!getPostMethods(method).contains(declaringMethod)) {
                        MethodEdge newEdge = new MethodEdge(method, declaringMethod);
                        listOfMethodEdges.add(newEdge);
                    }
                }
            }
        }
    }

    private void calculateCallDepth() {
        List<JMethodNode> startMethods = new ArrayList<>();
        for (JMethodNode method : listOfMethods) {
            if (getPreMethods(method).isEmpty()) {
                startMethods.add(method);
                method.setCallDepth(0);
            }
        }

        for (JMethodNode startMethod : startMethods) {
            List<JMethodNode> visitedMethods = new ArrayList<>();
            DFS(startMethod, visitedMethods, 0);
        }
    }

    private void DFS(JMethodNode node, List<JMethodNode> visitedNodes, int depth) {
        visitedNodes.add(node);
        depth++;
        if (node.getCallDepth() < depth) {
            node.setCallDepth(depth);
        } else {
            return;
        }
        for (JMethodNode postNode : getPostMethods(node)) {
            if (!visitedNodes.contains(postNode)) {
                DFS(postNode, visitedNodes, depth);
            }
        }
    }

    private void calculateCallDepthInComp() {
        List<JMethodNode> startMethods = new ArrayList<>();
        for (JMethodNode method : listOfMethods) {
            if (getPreMethods(method).isEmpty()) {
                startMethods.add(method);
                method.setCallDepthInComp(0);
            }
        }
        for (JMethodNode startMethod : startMethods) {
            List<JMethodNode> visitedMethods = new ArrayList<>();
            DFSInComp(startMethod, visitedMethods, 0);
        }
    }

    private void DFSInComp(JMethodNode node, List<JMethodNode> visitedNodes, int depth) {
        visitedNodes.add(node);
        depth++;
        if (node.getCallDepthInComp() < depth) {
            node.setCallDepthInComp(depth);
        } else {
            depth = node.getCallDepthInComp();
            return;
        }
        for (JMethodNode postNode : getPostMethods(node)) {
            if (!visitedNodes.contains(postNode)) {
                DFSInComp(postNode, visitedNodes, depth);
            }
        }
    }

    public List<JMethodNode> getListOfMethods() {
        return listOfMethods;
    }

    public List<MethodEdge> getListOfMethodEdges() {
        return listOfMethodEdges;
    }

    public JProjectNode getRoot() {
        return root;
    }

    public List<JMethodNode> getPreMethods(JMethodNode currentMethod) {
        List<JMethodNode> preMethods = new ArrayList<>();
        for (MethodEdge edge : listOfMethodEdges) {
            if (edge.getPostMethod().equals(currentMethod)) {
                preMethods.add(edge.getPreMethod());
            }
        }
        return preMethods;
    }

    public List<JMethodNode> getPostMethods(JMethodNode currentMethod) {
        List<JMethodNode> postMethods = new ArrayList<>();
        for (MethodEdge edge : listOfMethodEdges) {
            if (edge.getPreMethod().equals(currentMethod)) {
                postMethods.add(edge.getPostMethod());
            }
        }
        return postMethods;
    }

    public int getCallHierarchy(JMethodNode currentMethod) {
        return currentMethod.getCallDepth();
    }

    /**
     * ��ӡMDG
     */
    public void displayMDG() {
        // TODO Auto-generated method stub
        System.out.println("...  " + root);
        for (JMethodNode method : listOfMethods) {
            System.out.println(method + " calldepth:" + method.getCallDepth());
        }
        for (MethodEdge edge : listOfMethodEdges) {
            System.out.println(edge);
            System.out.println();
        }
    }
}

